<template>
  <div class="virtual-list-scroll-box" @scroll.passive="handleScroll">
    <div class="virtual-list-content-box">
      <div v-for="(item, index) in visibleListData" :style="item.style" :key="index" class="list-item">
        <slot name="listItem" :itemData="item.itemData" :index="index"></slot>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
  import { ref, computed, watchEffect } from 'vue';
  const props = withDefaults(
    defineProps<{
      // 数据列表
      data: any[];
      // 滚动容器的高度
      height: number;
      // 滚动容器的宽度
      width: number;
      // 每个数据项的高度
      itemHeight: number;
      // 数据项的数量
      itemCount: number;
    }>(),
    {
      data: () => [],
      height: 500,
      width: 200,
      itemHeight: 50,
      itemCount: 10,
    }
  );

  // 记录容器滚动卷去的高度
  const scrollOffset = ref(0);
  // 实时监听滚动距离
  const handleScroll = (evt: UIEvent) => {
    scrollOffset.value = (evt.currentTarget as HTMLDivElement).scrollTop;
  };

  // 上下缓冲区数据项偏移量
  const BUFF_OFFSET = 2;
  // 可视区能展示的元素的最大个数(向上取整)
  const VISIBLE_COUNT = Math.ceil(props.height / props.itemHeight);

  // 可视区域列表数据
  const visibleListData = computed(() => {
    const { data, itemHeight, itemCount } = props;
    // 可视区起始索引(向下取整)
    const startIndex = Math.floor(scrollOffset.value / itemHeight);
    // 顶部缓冲区起始索引(向上的时候索引号减去两个偏移量,最小偏移量是0)
    const startBuffIndex = Math.max(0, startIndex - BUFF_OFFSET);
    // 底部缓冲区结束索引(向下的时候索引号加上两个偏移量,最大索引号是数量总数量-1)
    const endBuffIndex = Math.min(startIndex + VISIBLE_COUNT + BUFF_OFFSET, itemCount - 1);

    return data.slice(startBuffIndex, endBuffIndex + 1).map((itemData, index) => {
      const offsetY = startBuffIndex + index;
      return {
        itemData,
        // 计算每个元素在滚动容器中的垂直偏移量
        style: { transform: `translateY(${itemHeight * offsetY}px)` },
      };
    });
  });

  // watchEffect(() => {
  //   console.log(visibleListData.value, props.data);
  // });
</script>

<style lang="less">
  // 外层容器
  .virtual-list-scroll-box {
    position: relative;
    width: v-bind('`${props.width}px`');
    height: v-bind('`${props.height}px`');
    overflow: auto;
    border: 1px solid #ccc;
    cursor: default;
    &::-webkit-scrollbar {
      display: none;
    }

    // 内容容器(高度大于外层容器,外层容器才能滚动)
    .virtual-list-content-box {
      height: v-bind('`${props.itemHeight * props.itemCount}px`');

      .list-item {
        position: absolute;
        width: 100%;
        height: v-bind('`${props.itemHeight}px`');
      }
    }
  }
</style>
